#include "Camera.h"

Camera::Camera()
{
	mViewDirty = true;
	mMoveSpeed = 2.f;
	mRotateSpeed = 0.1f;

	mWorldUp = glm::vec3(0.f, 1.f, 0.f);

	mView = glm::mat4(1.f);
	mProj = glm::mat4(1.f);

	mZmoView = glm::mat4(1.f);
	mZmoMat = glm::mat4(1.f);
	mZmoMat[1][1] = -1.f;
}

Camera::~Camera()
{

}

glm::vec4 Camera::GetPosition4f() const
{
	return glm::vec4(mPosition, 0.f);
}

glm::vec3 Camera::GetPosition() const
{
	return mPosition;
}

void Camera::SetPosition(float x, float y, float z)
{
	mPosition = glm::vec3(x, y, z);
	mViewDirty = true;
}

void Camera::SetPosition(const glm::vec3& v)
{
	mPosition = v;
	mViewDirty = true;
}

glm::vec3 Camera::GetRotation() const
{
	float x, y, z = 0;
	glm::extractEulerAngleXYX(mView, x, y, z);
	return glm::vec3(glm::degrees(x), glm::degrees(y), glm::degrees(z));
}

glm::vec4 Camera::GetRight4f() const
{
	return glm::vec4(mRight, 0.f);
}

glm::vec3 Camera::GetRight() const
{
	return mRight;
}

glm::vec4 Camera::GetUp4f() const
{
	return glm::vec4(mUp, 0.f);
}

glm::vec3 Camera::GetUp() const
{
	return mUp;
}

glm::vec4 Camera::GetLook4f() const
{
	return glm::vec4(mLook, 0.f);
}

glm::vec3 Camera::GetLook() const
{
	return mLook;
}

float Camera::GetNearZ() const
{
	return mNearZ;
}

float Camera::GetFarZ() const
{
	return mFarZ;
}

float Camera::GetAspect() const
{
	return mAspect;
}

float Camera::GetFovY() const
{
	return mFovY;
}

float Camera::GetFovX() const
{
	float halfWidth = 0.5f * GetNearWindowWidth();
	return 2.f * atan(halfWidth / mNearZ);
}

float Camera::GetNearWindowWidth() const
{
	return mAspect * mNearWindowHeight;
}

float Camera::GetNearWindowHeight() const
{
	return mNearWindowHeight;
}

float Camera::GetFarWindowWidth() const
{
	return mAspect * mFarWindowHeight;
}

float Camera::GetFarWindowHeight() const
{
	return mFarWindowHeight;
}

void Camera::SetLens(float fy, float ast, float zn, float zf)
{
	mFovY = fy;
	mAspect = ast;
	mNearZ = zn;
	mFarZ = zf;

	mNearWindowHeight = 2.f * mNearZ * tanf(0.5f * mFovY);
	mFarWindowHeight = 2.f * mFarZ * tanf(0.5f * mFovY);

	mProj = glm::perspective(mFovY, mAspect, mNearZ, mFarZ);
}

void Camera::LookAt(const glm::vec3& pos, const glm::vec3& target)
{
	mPosition = pos;
	mLook = glm::normalize(target - pos);
	mRight = glm::normalize(glm::cross(mLook, mWorldUp));
	mUp = glm::cross(mLook, mRight);

	mViewDirty = true;
}

glm::mat4 Camera::GetView() const
{
	assert(!mViewDirty);

	return mView;
}

glm::mat4 Camera::GetProj() const
{
	return mProj;
}

void Camera::SetSpeed(float moveSpeed, float rotateSpeed)
{
	mMoveSpeed = moveSpeed;
	mRotateSpeed = rotateSpeed;
}

void Camera::Strafe(float d)
{
	mPosition += mRight * d * mMoveSpeed;

	mViewDirty = true;
}

void Camera::Walk(float d)
{
	mPosition += mLook * d * mMoveSpeed;

	mViewDirty = true;
}

void Camera::Rise(float d)
{
	mPosition += glm::vec3(0.f, 1.f, 0.f) * d * mMoveSpeed;

	mViewDirty = true;
}

void Camera::Pitch(float angle)
{
	glm::mat4 rotM = glm::mat4(1.f);
	rotM = glm::rotate(rotM, glm::radians(angle * mRotateSpeed), mRight);
	mLook = glm::vec3(glm::vec4(mLook, 0.f) * rotM);
	mUp = glm::vec3(glm::vec4(mUp, 0.f) * rotM);

	mViewDirty = true;
}

void Camera::RotateY(float angle)
{
	glm::mat4 rotM = glm::mat4(1.f);
	rotM = glm::rotate(rotM, glm::radians(angle * mRotateSpeed), glm::vec3(0.f, 1.f, 0.f));
	mLook = glm::vec3(glm::vec4(mLook, 0.f) * rotM);
	mRight = glm::vec3(glm::vec4(mRight, 0.f) * rotM);

	mViewDirty = true;
}

void Camera::UpdateViewMatrix()
{
	if (mViewDirty)
	{
		mLook = glm::normalize(mLook);
		mUp = glm::normalize(glm::cross(mLook, mRight));
		mRight = glm::cross(mUp, mLook);

		mView = glm::lookAt(mPosition, mPosition + mLook, mUp);

		mZmoView = mZmoMat * mView;

		mViewDirty = false;
	}
}

Ray Camera::GetScreenRay(float x, float y) const
{
	assert(!mViewDirty);

	Ray ray;

	glm::mat4 viewProjInverse = glm::inverse(mProj * mView);

	x = 2.f * x - 1.f;
	y = 2.f * y - 1.f;

	glm::vec4 near = viewProjInverse * glm::vec4(x, y, 0.f, 1.f);
	near /= near.w;

	glm::vec4 far = viewProjInverse * glm::vec4(x, y, 1.f, 1.f);
	far /= far.w;

	ray.mOrigin = glm::vec3(near);
	ray.mDirection = glm::normalize(glm::vec3(far) - ray.mOrigin);

	return ray;
}

glm::vec3 Camera::GetScreenPos(float x, float y, float depth) const
{
	assert(!mViewDirty);

	glm::mat4 viewProjInverse = glm::inverse(mProj * mView);

	x = 2.f * x - 1.f;
	y = 2.f * y - 1.f;

	glm::vec4 near = viewProjInverse * glm::vec4(x, y, 0.f, 1.f);
	near /= near.w;

	glm::vec4 far = viewProjInverse * glm::vec4(x, y, 1.f, 1.f);
	far /= far.w;

	glm::vec3 direction = glm::normalize(glm::vec3(far) - glm::vec3(near));

	return glm::vec3(near) + direction * depth;
}
